/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package genesisblock

import (
	"encoding/hex"
	"errors"
	"fmt"
	"regexp"
	"strconv"

	clicommon "hundsun.com/hsl/hschain/cmd/cli/common"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/protos/consensus/raft"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/protos/parachain"
)

var regexpNonNegativeInt = regexp.MustCompile(string("^[1-9]\\d*|[0]$"))
var regexpPositiveInt = regexp.MustCompile(string("^([1-9]|[1-2]\\d|3[0-2])$"))

// BaseGenesisBlockYamlConfig 创世块配置文件基类（不包含consensus字段）
type BaseGenesisBlockYamlConfig struct {
	ChainID        string                          `yaml:"chainID"`
	Timestamp      string                          `yaml:"timestamp"`
	Difficulty     string                          `yaml:"difficulty"`
	ParentHash     string                          `yaml:"parentHash"`
	Reward         string                          `yaml:"reward"`
	GasLimit       string                          `yaml:"gasLimit"`
	Crypto         *CryptoConfig                   `yaml:"crypto"`
	CheckBlock     *CheckBlockConfig               `yaml:"checkBlock"`
	SequenceCenter *clicommon.SequenceCenterConfig `yaml:"sequenceCenter"`
	BlockLimit     *BlockLimitConfig               `yaml:"blockLimit"`
	TimeLimit      *TimeLimitConfig                `yaml:"timeLimit"`
	Alloc          []*clicommon.BalanceAllocConfig `yaml:"alloc"`
	Manager        *ManagerConfig                  `yaml:"manager"`
	FeeLimit       *FeeLimitConfig                 `yaml:"feeLimit"`
	//下列属性是Parse之后才有的
	timestamp        int64
	parentHash       []byte
	genesisTxPayload *config.GenesisTransactionPayload
}

// ConvertToProtoMessage 主要是将对象解析为GenesisTransactionPayload（不含consensus字段的解析）
func (b *BaseGenesisBlockYamlConfig) ConvertToProtoMessage() error {
	var err error
	if b.Timestamp == "" {
		return errors.New("'timestamp' field cannot be empty")
	}
	if !regexpNonNegativeInt.MatchString(b.Timestamp) {
		return errors.New("'timestamp' field  must be zero or a positive number")
	}
	b.timestamp, err = strconv.ParseInt(b.Timestamp, 10, 64)
	if err != nil {
		return err
	}
	if b.Difficulty == "" {
		return errors.New("'difficulty' field cannot be empty")
	}

	if !regexpNonNegativeInt.MatchString(b.Difficulty) {
		return errors.New("'difficulty' field  must be zero or a positive number")
	}
	difficulty, err := strconv.ParseUint(b.Difficulty, 10, 64)
	if err != nil {
		return err
	}

	if b.Reward == "" {
		return errors.New("reward config cannot be empty")
	}
	if !regexpNonNegativeInt.MatchString(b.Reward) {
		return errors.New("'reward' field  must be zero or a positive number")
	}
	reward, err := strconv.ParseUint(b.Reward, 10, 64)
	if err != nil {
		return err
	}

	if b.GasLimit == "" {
		b.GasLimit = "100000"
	}
	if !regexpNonNegativeInt.MatchString(b.GasLimit) {
		return errors.New("'reward' field  must be zero or a positive number")
	}
	gasLimit, err := strconv.ParseUint(b.GasLimit, 10, 64)
	if err != nil {
		return err
	}

	if b.Crypto == nil {
		return errors.New("crypto config cannot be empty")
	}

	if b.CheckBlock == nil {
		return errors.New("check block config cannot be empty")
	}

	if b.CheckBlock.Enabled == "" {
		return errors.New("'enabled' field of 'checkBlock' section cannot be empty")
	}

	if b.CheckBlock.Enabled != "true" && b.CheckBlock.Enabled != "false" {
		return errors.New("value of 'enabled' field of 'checkBlock' section must be true or false")
	}

	checkBlockEnabled, _ := strconv.ParseBool(b.CheckBlock.Enabled)
	if !checkBlockEnabled {
		return errors.New("value of 'enable' field of 'checkBlock' section must be true")
	}

	if b.CheckBlock.Distance == "" {
		return errors.New("'distance' field of 'checkBlock' section cannot be empty")
	}
	if !regexpPositiveInt.MatchString(b.CheckBlock.Distance) {
		return errors.New("value of 'distance' field of 'checkBlock' section should be integer and in range [1,32]")
	}
	checkBlockDistance, err := strconv.ParseUint(b.CheckBlock.Distance, 10, 64)
	if err != nil {
		return err
	}

	if b.BlockLimit == nil {
		return errors.New("blockLimit config cannot be empty")
	}

	if b.TimeLimit == nil {
		return errors.New("timeLimit config cannot be empty")
	}

	// 哈希长度的校验依赖crypto初始化，所以先校验crypto配置来触发crypto初始化
	pbCryptoConfig := &config.CryptoConfig{
		AsymmetricAlgorithm: b.Crypto.AsymmetricAlgorithm,
		HashFamily:          b.Crypto.HashFamily,
		SymmetricAlgorithm:  b.Crypto.SymmetricAlgorithm,
	}

	hashSize, err := crypto.HashSize()
	if err != nil {
		panic(err)
	}
	hashHexRegexp := regexp.MustCompile("^[a-zA-Z0-9]{" + strconv.Itoa(hashSize*2) + "}$")
	if !hashHexRegexp.MatchString(b.ParentHash) {
		return fmt.Errorf("parentHash must match pattern '%s'", hashHexRegexp.String())
	}
	if !hashHexRegexp.MatchString(b.ParentHash) {
		return errors.New("parentHash must match pattern " + hashHexRegexp.String())
	}
	b.parentHash, _ = hex.DecodeString(b.ParentHash)

	//seqCenterNetAddr := &common.NetworkAddress{}
	//err = seqCenterNetAddr.Parse(b.SequenceCenter.NetworkAddress)
	//if err != nil {
	//	return err
	//}

	pbGenesisTxPayload := &config.GenesisTransactionPayload{
		ChainID:    b.ChainID,
		Crypto:     pbCryptoConfig,
		Difficulty: difficulty,
		Reward:     reward,
		GasLimit:   gasLimit,
		CheckBlock: &config.CheckBlockConfig{
			Enabled:  checkBlockEnabled,
			Distance: checkBlockDistance,
		},
		Consensus: nil,
		BlockLimit: &config.BlockLimit{
			MaxTxSizeBytes:    b.BlockLimit.MaxTxSizeBytes,
			MaxTxCount:        b.BlockLimit.MaxTxCount,
			MaxBlockSizeBytes: b.BlockLimit.MaxBlockSizeBytes,
		},
		TimeLimit: &config.TimeLimit{
			MaxTxTTL:             b.TimeLimit.MaxTxTTL,
			MaxTimeIntervalError: b.TimeLimit.MaxTimeIntervalError,
		},
		Alloc: nil,
	}

	if b.SequenceCenter != nil {
		pbGenesisTxPayload.SequenceCenter = &config.SequenceCenterConfig{
			PeerID:        b.SequenceCenter.PeerID,
			SignerAddress: b.SequenceCenter.SignerAddress,
		}
	}

	pbGenesisTxPayload.Alloc = make([]*common.BalanceAlloc, len(b.Alloc))
	for i, a := range b.Alloc {
		pbGenesisTxPayload.Alloc[i] = &common.BalanceAlloc{
			Address: a.Address,
			Balance: a.Balance,
		}
	}
	if b.Manager != nil {
		pbGenesisTxPayload.Manager = &config.ManagerConfig{}
		pbGenesisTxPayload.Manager.Addrs = append(pbGenesisTxPayload.Manager.Addrs, b.Manager.Addresses...)
	}

	if b.FeeLimit != nil {
		pbGenesisTxPayload.FeeLimit = &config.FeeLimit{MinTxFee: b.FeeLimit.MinTxFee, MaxBlockFee: b.FeeLimit.MaxBlockFee}
	}

	b.genesisTxPayload = pbGenesisTxPayload
	return nil
}

// InitFromProtoMessage 从创世块初始化（不包含consensus字段），由调用者做创世块合法性校验
func (b *BaseGenesisBlockYamlConfig) InitFromProtoMessage(genesisBlock *ledger.Block) error {
	genesisTxPayload, _ := pb.ParseGenesisTransactionPayload(genesisBlock.Transactions[0].Payload)
	b.genesisTxPayload = genesisTxPayload
	b.timestamp = genesisBlock.Header.BlockTime
	b.parentHash = genesisBlock.Header.ParentHash

	b.ChainID = genesisTxPayload.ChainID
	b.Timestamp = strconv.FormatInt(genesisBlock.Header.BlockTime, 10)
	b.Difficulty = strconv.FormatUint(genesisTxPayload.Difficulty, 10)
	b.Reward = strconv.FormatUint(genesisTxPayload.Reward, 10)
	b.GasLimit = strconv.FormatUint(genesisTxPayload.GasLimit, 10)
	b.ParentHash = hex.EncodeToString(genesisBlock.Header.ParentHash)
	b.Crypto = &CryptoConfig{
		AsymmetricAlgorithm: genesisTxPayload.Crypto.AsymmetricAlgorithm,
		HashFamily:          genesisTxPayload.Crypto.HashFamily,
		SymmetricAlgorithm:  genesisTxPayload.Crypto.SymmetricAlgorithm,
	}
	b.CheckBlock = &CheckBlockConfig{
		Enabled:  strconv.FormatBool(genesisTxPayload.CheckBlock.Enabled),
		Distance: strconv.FormatUint(genesisTxPayload.CheckBlock.Distance, 10),
	}
	if genesisTxPayload.SequenceCenter != nil {
		b.SequenceCenter = &clicommon.SequenceCenterConfig{
			PeerID:        genesisTxPayload.SequenceCenter.PeerID,
			SignerAddress: genesisTxPayload.SequenceCenter.SignerAddress,
		}
	}

	b.BlockLimit = &BlockLimitConfig{
		MaxTxSizeBytes:    genesisTxPayload.BlockLimit.MaxTxSizeBytes,
		MaxTxCount:        genesisTxPayload.BlockLimit.MaxTxCount,
		MaxBlockSizeBytes: genesisTxPayload.BlockLimit.MaxBlockSizeBytes,
	}
	b.TimeLimit = &TimeLimitConfig{
		MaxTxTTL:             genesisTxPayload.TimeLimit.MaxTxTTL,
		MaxTimeIntervalError: genesisTxPayload.TimeLimit.MaxTimeIntervalError,
	}
	b.Alloc = make([]*clicommon.BalanceAllocConfig, len(genesisTxPayload.Alloc))
	for i, a := range genesisTxPayload.Alloc {
		b.Alloc[i] = &clicommon.BalanceAllocConfig{
			Address: a.Address,
			Balance: a.Balance,
		}
	}
	if genesisTxPayload.Manager != nil {
		b.Manager = &ManagerConfig{}
		b.Manager.Addresses = append(b.Manager.Addresses, genesisTxPayload.Manager.Addrs...)
	}

	if genesisTxPayload.FeeLimit != nil {
		b.FeeLimit = &FeeLimitConfig{MinTxFee: genesisTxPayload.GetFeeLimit().GetMinTxFee(), MaxBlockFee: genesisTxPayload.GetFeeLimit().GetMaxBlockFee()}
	}

	return nil
}

//GetTimestamp get timestamp
func (b *BaseGenesisBlockYamlConfig) GetTimestamp() int64 {
	return b.timestamp
}

//GetParentHash get parent hash
func (b *BaseGenesisBlockYamlConfig) GetParentHash() []byte {
	return b.parentHash
}

//GetGenesisTransactionPayload get genesis transaction payload
func (b *BaseGenesisBlockYamlConfig) GetGenesisTransactionPayload() *config.GenesisTransactionPayload {
	return b.genesisTxPayload
}

// CommonGenesisBlockYamlConfig 通用的创世块yaml文件对应的结构体（consensus字段仅包含type属性）
type CommonGenesisBlockYamlConfig struct {
	BaseGenesisBlockYamlConfig `yaml:",inline"`
	Consensus                  *BaseConsensusConfig `yaml:"consensus"`
}

//ConvertToProtoMessage convert to proto message
func (c *CommonGenesisBlockYamlConfig) ConvertToProtoMessage() error {
	err := c.BaseGenesisBlockYamlConfig.ConvertToProtoMessage()
	if err != nil {
		return err
	}
	if c.Consensus == nil {
		return errors.New("consensus config cannot be empty")
	}

	consensusType := c.Consensus.Type
	if len(consensusType) == 0 {
		return errors.New("type of consensus config cannot be empty")
	}

	if consensusType != config.DposConsensusType && consensusType != config.SoloConsensusType &&
		consensusType != raft.ConsensusType && consensusType != parachain.ConsensusType {
		return fmt.Errorf("unsupported consensus type '%s'", consensusType)
	}
	return nil
}

// BaseConsensusConfig consensus配置的基类（仅包含type属性）
type BaseConsensusConfig struct {
	Type string `yaml:"type"`
}

//CryptoConfig crypto config
type CryptoConfig struct {
	AsymmetricAlgorithm string `yaml:"asymmetricAlgorithm"`
	HashFamily          string `yaml:"hashFamily"`
	SymmetricAlgorithm  string `yaml:"symmetricAlgorithm"`
}

//CheckBlockConfig check block config
type CheckBlockConfig struct {
	Enabled  string `yaml:"enabled"`
	Distance string `yaml:"distance"`
}

//BlockLimitConfig block limit config
type BlockLimitConfig struct {
	MaxTxSizeBytes    uint32 `yaml:"maxTxSizeBytes"`
	MaxTxCount        uint32 `yaml:"maxTxCount"`
	MaxBlockSizeBytes uint32 `yaml:"maxBlockSizeBytes"`
}

//TimeLimitConfig time limit config
type TimeLimitConfig struct {
	MaxTxTTL             uint32 `yaml:"maxTxTTL"`
	MaxTimeIntervalError uint32 `yaml:"maxTimeIntervalErr"`
}

//ParaChainConfig parachain config
type ParaChainConfig struct {
	MaxChainCount             string `yaml:"maxChainCount"`
	MaxValidatorCountPerChain string `yaml:"maxValidatorCountPerChain"`
	ValidatorDeposit          string `yaml:"validatorDeposit"`
}

//ManagerConfig manager config
type ManagerConfig struct {
	Addresses []string `yaml:"addresses"`
}

//FeeLimitConfig fee limit config
type FeeLimitConfig struct {
	MinTxFee    uint64 `yaml:"minTxFee"`
	MaxBlockFee uint64 `yaml:"maxBlockFee"`
}

//ConvertToProtoMessage convert to proto message
func (p *ParaChainConfig) ConvertToProtoMessage() (*config.ParaChainConfig, error) {
	maxChainCount, err := stringToUint32("paraChain.maxChainCount", p.MaxChainCount)
	if err != nil {
		return nil, err
	}
	maxValidatorCountPerChain, err := stringToUint32("paraChain.maxValidatorCountPerChain", p.MaxValidatorCountPerChain)
	if err != nil {
		return nil, err
	}
	validatorDeposit, err := stringToUint64("paraChain.ValidatorDeposit", p.ValidatorDeposit)
	if err != nil {
		return nil, err
	}
	return &config.ParaChainConfig{
		MaxChainCount:             maxChainCount,
		MaxValidatorCountPerChain: maxValidatorCountPerChain,
		ValidatorDeposit:          validatorDeposit,
	}, nil
}

func stringToUint32(propName, propValue string) (uint32, error) {
	result, err := stringToUint64(propName, propValue)
	if err != nil {
		return 0, err
	}
	return uint32(result), err
}

func stringToUint64(propName, propValue string) (uint64, error) {
	if len(propValue) == 0 {
		return 0, fmt.Errorf("'%s' not found or is empty", propName)
	}
	result, err := strconv.ParseUint(propValue, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("'%s' cannot be converted to uint32", propName)
	}
	return result, nil
}
